# załadowanie modelu
import pickle
model = pickle.load(open("../../../../WB-XAI-Projekt/RF_model", "rb"))
import pandas as pd
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import LabelEncoder, OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.model_selection import train_test_split
# Wczytanie i przygotowanie danych
full_data = pd.read_csv("hotel_bookings.csv")
full_data["agent"] = full_data["agent"].astype(str)
treshold = 0.005 * len(full_data)
agents_to_change = full_data['agent'].value_counts()[full_data['agent'].value_counts() < treshold].index
full_data.loc[full_data["agent"].isin(agents_to_change), "agent"] = "other"
countries_to_change = full_data['country'].value_counts()[full_data['country'].value_counts() < treshold].index
full_data.loc[full_data["country"].isin(countries_to_change), "country"] = "other"
# Określenie cech uwzględnionych w modelu
num_features = ["lead_time", "arrival_date_week_number",
"stays_in_weekend_nights", "stays_in_week_nights",
"adults", "previous_cancellations",
"previous_bookings_not_canceled",
"required_car_parking_spaces", "total_of_special_requests",
"adr", "booking_changes"]
cat_features = ["hotel", "market_segment", "country",
"reserved_room_type",
"customer_type", "agent"]
features = num_features + cat_features
# Podział na zmienne wyjaśniające i target
X = full_data.drop(["is_canceled"], axis=1)[features]
y = full_data["is_canceled"]
categorical_names = {}
for feature in cat_features:
col = X[[feature]]
cat_transformer = SimpleImputer(strategy="constant", fill_value="Unknown")
col = cat_transformer.fit_transform(col)
X[feature] = col
le = LabelEncoder()
le.fit(X[[feature]])
X[[feature]] = le.transform(X[[feature]])
categorical_names[feature] = le.classes_
categorical_names
# Preprocessing
num_transformer = SimpleImputer(strategy="constant")
preprocessor = ColumnTransformer(transformers=[("num", num_transformer, num_features)],
remainder = 'passthrough')
for feature in num_features:
X[feature] = X[feature].astype(float)
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.2, random_state=42)
selected_X = X_train.iloc[[420]]
selected_y = y_train.iloc[420]
predicted_y = model.predict(selected_X)
print("Prawdziwa wartość:", selected_y)
print("Przewidziana wartość:", predicted_y)
selected_X.head()
Nasz model znowu poprawnie przewidział nieodwołanie rezerwacji.
import dalex as dx
explainer = dx.Explainer(model, X_train, y_train, label = "Random Forest")
cp = explainer.predict_profile(selected_X)
cp.plot()
print(categorical_names["country"][16])
print(categorical_names["country"][17])
Widzimy, że dla wybranej obserwacji prawdopodobieństwo rezygnacji jest niemalże zerowe (co jest zgodne z poprzednim zadaniem domowym, tam prawdopodobieństwo rezygnacji dla tej obserwacji wynosiło 1%). Jest tylko jedna zmienna, która mogłaby zmienić predykcję modelu dla tej obserwacji, gdyby sama została zmieniona - jest to previous_cancellations (co jest w sumie dość logicznym wnioskiem). Dość wysokie podbicie prawdopodobieństwa daje jeszcze zmiana kraju (swoją drogą tu znowu jest problem z kodowaniem zmiennych kategorycznych, przy one hot encodingu działa, przy label encodingu już nie). Mimo interpretacji ciągłej widać, że krajem, dla którego wzrasta prawdopodobieństwo rezygnacji jest Portugalia. A krajem, w którym znajdują się analizowane hotele też jest Portugalia. Wniosek? Turyści z zagranicy rzadziej rezygnują. Przy okazji warto zauważyć, że dość duże podbicie rezygnacji jest dla kraju nr 17. Można się zastanawiać, czy to kwestia tego, że ma numer bliski 16 i wpływ się przeniósł trochę dalej, czy to po prostu kwestia tego, że to Rosja.
Przy innych zmiennych raczej nie dzieje się nic ciekawego. Choć warto wspomnieć o zmiennej lead_time, której wzrost do pewnego momentu (mniej więcej do roku) daje niemal liniowy wzrost prawdopodobieństwa rezygnacji. No i brak specjalnych żądań zwiększa prawdopodobieństwo rezygnacji (co już też zostało potwierdzone przez poprzednie prace domowe).
X1 = X_train.iloc[[420, 2137], :] #total of special requests
X2 = X_train.iloc[[420, 13], :] #stays in weekend nights
X3 = X_train.iloc[[420, 72], :] #stays in weekend nights
cp = explainer.predict_profile(X3)
cp.plot()
Przez to, że na naszych wykresach niewiele się dzieje (a tam gdzie coś się dzieje, to zazwyczaj bardzo podobnie), trudno było znaleźć odwrotny wpływ jakiejś zmiennej. Udało mi się znaleźć 2 dość dobre przykłady - oba w porównaniu do obserwacji z poprzedniego podpunktu.
Dla obserwacji X3 stays_in_weekend_nights wraz ze wzrostem wpływa negatywnie na prawdopodobieństwo rezygnacji, mimo że dla poprzedniej obserwacji wpływało pozytywnie.
cp = explainer.predict_profile(X1)
cp.plot()
Ten przypadek jest moim zdaniem ciekawszy, bo "schodek" przy zmiennej total_of_special_requests jest w drugą stronę niż w obu poprzednich przykładach. Czyli czasem brak specjalnych żądań wpływa pozytywnie na prawdopodobieństwo braku rezygnacji.
Wnioski: